import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import uuid
import shutil
import threading
import pathlib
from datetime import datetime

class FileBackupTool:
    def __init__(self, root):
        self.root = root
        self.root.title("文件备份工具")
        self.root.geometry("800x650")
        self.root.resizable(True, True)
        
        # 设置中文字体支持
        self.root.option_add("*Font", "SimHei 10")
        
        # 创建主框架
        self.main_frame = ttk.Frame(root, padding="20")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        
        # ==================== 必要文件夹（全部子目录）====================
        ttk.Label(self.main_frame, text="必要文件夹（所有子目录将被备份）:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.required_dir_var = tk.StringVar()
        ttk.Entry(self.main_frame, textvariable=self.required_dir_var, width=50).grid(row=0, column=1, pady=5)
        ttk.Button(self.main_frame, text="浏览...", command=self.select_required_dir).grid(row=0, column=2, padx=5, pady=5)
        
        # 必要文件夹内容预览
        ttk.Label(self.main_frame, text="必要文件夹子目录:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.req_tree = ttk.Treeview(self.main_frame, columns=("1", "2"), show="headings")
        self.req_tree.heading("1", text="文件夹名称")
        self.req_tree.heading("2", text="修改时间")
        self.req_tree.column("1", width=300)
        self.req_tree.column("2", width=150)
        scrollbar1 = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.req_tree.yview)
        self.req_tree.configure(yscroll=scrollbar1.set)
        self.req_tree.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        scrollbar1.grid(row=2, column=3, sticky=(tk.N, tk.S))
        
        # ==================== 可选文件夹（灵活筛选）====================
        ttk.Label(self.main_frame, text="可选文件夹（可选择子目录）:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.optional_dir_var = tk.StringVar()
        ttk.Entry(self.main_frame, textvariable=self.optional_dir_var, width=50).grid(row=3, column=1, pady=5)
        ttk.Button(self.main_frame, text="浏览...", command=self.select_optional_dir).grid(row=3, column=2, padx=5, pady=5)
        
        # 可选文件夹筛选选项
        ttk.Label(self.main_frame, text="可选文件夹筛选选项:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.optional_option = tk.StringVar(value="all")
        
        option_frame = ttk.Frame(self.main_frame)
        option_frame.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        ttk.Radiobutton(option_frame, text="全部备份", variable=self.optional_option, value="all").pack(anchor=tk.W)
        ttk.Radiobutton(option_frame, text="全部不备份", variable=self.optional_option, value="none").pack(anchor=tk.W)
        
        self.first_n_frame = ttk.Frame(option_frame)
        ttk.Radiobutton(option_frame, text="前 N 个文件夹", variable=self.optional_option, value="first_n").pack(anchor=tk.W)
        ttk.Label(self.first_n_frame, text="N:").pack(side=tk.LEFT, padx=5)
        self.first_n_var = tk.IntVar(value=10)
        ttk.Entry(self.first_n_frame, textvariable=self.first_n_var, width=5).pack(side=tk.LEFT)
        self.first_n_frame.pack(anchor=tk.W, padx=20)
        
        self.last_n_frame = ttk.Frame(option_frame)
        ttk.Radiobutton(option_frame, text="后 N 个文件夹", variable=self.optional_option, value="last_n").pack(anchor=tk.W)
        ttk.Label(self.last_n_frame, text="N:").pack(side=tk.LEFT, padx=5)
        self.last_n_var = tk.IntVar(value=10)
        ttk.Entry(self.last_n_frame, textvariable=self.last_n_var, width=5).pack(side=tk.LEFT)
        self.last_n_frame.pack(anchor=tk.W, padx=20)
        
        self.random_n_frame = ttk.Frame(option_frame)
        ttk.Radiobutton(option_frame, text="随机选择 N 个文件夹", variable=self.optional_option, value="random_n").pack(anchor=tk.W)
        ttk.Label(self.random_n_frame, text="N:").pack(side=tk.LEFT, padx=5)
        self.random_n_var = tk.IntVar(value=10)
        ttk.Entry(self.random_n_frame, textvariable=self.random_n_var, width=5).pack(side=tk.LEFT)
        self.random_n_frame.pack(anchor=tk.W, padx=20)
        
        # 可选文件夹内容预览
        ttk.Label(self.main_frame, text="可选文件夹子目录:").grid(row=6, column=0, sticky=tk.W, pady=5)
        self.opt_tree = ttk.Treeview(self.main_frame, columns=("1", "2"), show="headings")
        self.opt_tree.heading("1", text="文件夹名称")
        self.opt_tree.heading("2", text="修改时间")
        self.opt_tree.column("1", width=300)
        self.opt_tree.column("2", width=150)
        scrollbar2 = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.opt_tree.yview)
        self.opt_tree.configure(yscroll=scrollbar2.set)
        self.opt_tree.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        scrollbar2.grid(row=7, column=3, sticky=(tk.N, tk.S))
        
        # ==================== UUID命名选项 ====================
        self.use_uuid_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(self.main_frame, text="使用UUID命名备份文件夹", variable=self.use_uuid_var).grid(row=8, column=0, sticky=tk.W, pady=10)
        
        # ==================== 保存路径 ====================
        ttk.Label(self.main_frame, text="备份保存路径:").grid(row=9, column=0, sticky=tk.W, pady=5)
        self.save_path_var = tk.StringVar()
        ttk.Entry(self.main_frame, textvariable=self.save_path_var, width=50).grid(row=9, column=1, pady=5)
        ttk.Button(self.main_frame, text="浏览...", command=self.select_save_folder).grid(row=9, column=2, padx=5, pady=5)
        
        # ==================== 处理控制 ====================
        ttk.Button(self.main_frame, text="开始备份", command=self.start_backup).grid(row=10, column=1, pady=20)
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(self.main_frame, variable=self.progress_var, length=100)
        self.progress_bar.grid(row=11, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        # 日志区域
        ttk.Label(self.main_frame, text="备份日志:").grid(row=12, column=0, sticky=tk.W, pady=5)
        self.log_text = tk.Text(self.main_frame, height=8, width=80)
        self.log_text.grid(row=13, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        scrollbar_log = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.log_text.yview)
        self.log_text.configure(yscroll=scrollbar_log.set)
        scrollbar_log.grid(row=13, column=3, sticky=(tk.N, tk.S))
        
        # 配置网格权重
        self.main_frame.columnconfigure(1, weight=1)
        self.main_frame.rowconfigure(2, weight=1)    # 必要文件夹预览区域
        self.main_frame.rowconfigure(7, weight=1)    # 可选文件夹预览区域
        self.main_frame.rowconfigure(13, weight=1)   # 日志区域
        
        # 初始化变量
        self.required_folders = []  # 必要文件夹的所有子目录
        self.optional_folders = []  # 可选文件夹的所有子目录
        self.backup_list = []       # 最终待备份的目录列表
        
        # 记录开始时间
        self.start_time = None
    
    def log(self, message):
        """向日志区域添加消息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
    
    # ==================== 必要文件夹处理 ====================
    def select_required_dir(self):
        """选择必要文件夹"""
        folder_path = filedialog.askdirectory(title="选择必要文件夹（所有子目录将被备份）")
        if folder_path:
            self.required_dir_var.set(folder_path)
            self.load_required_dir_folders()
    
    def load_required_dir_folders(self):
        """加载并显示必要文件夹的所有子目录"""
        self.log("正在加载必要文件夹的子目录...")
        self.required_folders = []
        
        # 清空现有内容
        for item in self.req_tree.get_children():
            self.req_tree.delete(item)
        
        req_path = pathlib.Path(self.required_dir_var.get())
        if not req_path.exists() or not req_path.is_dir():
            self.log("错误: 必要文件夹不存在或不是一个目录")
            return
        
        try:
            # 获取所有子文件夹并按名称排序
            self.required_folders = sorted(
                [f for f in req_path.iterdir() if f.is_dir()],
                key=lambda x: x.name
            )
            
            # 显示文件夹信息
            for folder in self.required_folders:
                try:
                    stats = folder.stat()
                    modified = datetime.fromtimestamp(stats.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
                    self.req_tree.insert("", tk.END, values=(folder.name, modified))
                except Exception as e:
                    self.log(f"无法获取文件夹 {folder.name} 的信息: {str(e)}")
            
            self.log(f"必要文件夹共加载 {len(self.required_folders)} 个子目录（将全部备份）")
        except Exception as e:
            self.log(f"加载必要文件夹内容时出错: {str(e)}")
    
    # ==================== 可选文件夹处理 ====================
    def select_optional_dir(self):
        """选择可选文件夹"""
        folder_path = filedialog.askdirectory(title="选择可选文件夹（可选择子目录）")
        if folder_path:
            self.optional_dir_var.set(folder_path)
            self.load_optional_dir_folders()
    
    def load_optional_dir_folders(self):
        """加载并显示可选文件夹的所有子目录"""
        self.log("正在加载可选文件夹的子目录...")
        self.optional_folders = []
        
        # 清空现有内容
        for item in self.opt_tree.get_children():
            self.opt_tree.delete(item)
        
        opt_path = pathlib.Path(self.optional_dir_var.get())
        if not opt_path.exists() or not opt_path.is_dir():
            self.log("错误: 可选文件夹不存在或不是一个目录")
            return
        
        try:
            # 获取所有子文件夹并按名称排序
            self.optional_folders = sorted(
                [f for f in opt_path.iterdir() if f.is_dir()],
                key=lambda x: x.name
            )
            
            # 显示文件夹信息
            for folder in self.optional_folders:
                try:
                    stats = folder.stat()
                    modified = datetime.fromtimestamp(stats.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
                    self.opt_tree.insert("", tk.END, values=(folder.name, modified))
                except Exception as e:
                    self.log(f"无法获取文件夹 {folder.name} 的信息: {str(e)}")
            
            self.log(f"可选文件夹共加载 {len(self.optional_folders)} 个子目录（可筛选备份）")
        except Exception as e:
            self.log(f"加载可选文件夹内容时出错: {str(e)}")
    
    # ==================== 保存路径选择 ====================
    def select_save_folder(self):
        """选择保存路径"""
        folder_path = filedialog.askdirectory(title="选择备份保存路径")
        if folder_path:
            self.save_path_var.set(folder_path)
    
    # ==================== 输入验证 ====================
    def validate_inputs(self):
        """验证输入是否有效"""
        req_path = self.required_dir_var.get()
        save_path = self.save_path_var.get()
        
        # 验证必要文件夹
        if not req_path or not pathlib.Path(req_path).exists():
            messagebox.showerror("错误", "请选择有效的必要文件夹")
            return False
        
        if not self.required_folders:
            messagebox.showerror("错误", "必要文件夹中没有可备份的子目录")
            return False
        
        # 验证保存路径
        if not save_path:
            messagebox.showerror("错误", "请选择备份保存路径")
            return False
        
        # 可选文件夹可选，如果选择了则验证
        opt_path = self.optional_dir_var.get()
        if opt_path and not pathlib.Path(opt_path).exists():
            messagebox.showerror("错误", "请选择有效的可选文件夹")
            return False
        
        return True
    
    # ==================== 筛选可选文件夹 ====================
    def filter_optional_folders(self):
        """根据选择筛选可选文件夹的子目录"""
        option = self.optional_option.get()
        filtered = []
        total = len(self.optional_folders)
        
        if not self.optional_folders:
            self.log("可选文件夹中没有子目录，不进行备份")
            return filtered
            
        if option == "all":
            filtered = self.optional_folders
            self.log(f"可选文件夹选择全部 {total} 个子目录")
            
        elif option == "none":
            self.log("可选文件夹选择全部不备份")
            
        elif option == "first_n":
            n = self.first_n_var.get()
            n = min(n, total)  # 防止n大于实际数量
            filtered = self.optional_folders[:n]
            self.log(f"可选文件夹选择前 {n} 个子目录")
            
        elif option == "last_n":
            n = self.last_n_var.get()
            n = min(n, total)  # 防止n大于实际数量
            filtered = self.optional_folders[-n:]
            self.log(f"可选文件夹选择后 {n} 个子目录")
            
        elif option == "random_n":
            n = self.random_n_var.get()
            n = min(n, total)  # 防止n大于实际数量
            filtered = random.sample(self.optional_folders, n)
            self.log(f"可选文件夹随机选择 {n} 个子目录")
        
        return filtered
    
    # ==================== 开始备份 ====================
    def start_backup(self):
        """开始备份文件夹"""
        if not self.validate_inputs():
            return
        
        # 构建待备份列表：必要文件夹全部 + 可选文件夹筛选结果
        self.backup_list = self.required_folders.copy()
        optional_filtered = self.filter_optional_folders()
        self.backup_list.extend(optional_filtered)
        
        total_count = len(self.backup_list)
        if total_count == 0:
            messagebox.showinfo("提示", "没有需要备份的文件夹")
            return
        
        # 确认对话框
        use_uuid_text = "使用UUID命名" if self.use_uuid_var.get() else "使用原始名称"
        result = messagebox.askyesno(
            "确认备份", 
            f"即将备份共 {total_count} 个文件夹\n"
            f"其中：必要文件夹 {len(self.required_folders)} 个，可选文件夹 {len(optional_filtered)} 个\n\n"
            f"备份命名方式: {use_uuid_text}\n"
            f"保存路径: {self.save_path_var.get()}"
        )
        
        if not result:
            self.log("操作已取消")
            return
        
        # 清空日志和进度条
        self.log_text.delete(1.0, tk.END)
        self.progress_var.set(0)
        
        # 在新线程中执行备份，避免界面卡顿
        self.start_time = datetime.now()
        self.log(f"开始备份... 时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
        self.log(f"共需备份 {total_count} 个文件夹")
        
        thread = threading.Thread(target=self.perform_backup)
        thread.daemon = True
        thread.start()
    
    def perform_backup(self):
        """执行备份操作的主函数"""
        try:
            save_path = pathlib.Path(self.save_path_var.get())
            save_path.mkdir(parents=True, exist_ok=True)
            
            total_folders = len(self.backup_list)
            success_count = 0
            fail_count = 0
            
            for i, folder in enumerate(self.backup_list, 1):
                try:
                    # 更新进度条
                    progress = (i / total_folders) * 100
                    self.progress_var.set(progress)
                    self.root.update_idletasks()
                    
                    # 确定目标文件夹名称
                    if self.use_uuid_var.get():
                        target_name = str(uuid.uuid4())
                    else:
                        target_name = folder.name
                    
                    target_path = save_path / target_name
                    
                    # 执行复制操作
                    self.log(f"正在备份: {folder} -> {target_path}")
                    shutil.copytree(folder, target_path)
                    
                    self.log(f"成功备份: {folder.name}")
                    success_count += 1
                    
                except Exception as e:
                    self.log(f"备份失败: {folder.name} - 错误: {str(e)}")
                    fail_count += 1
            
            # 备份完成
            end_time = datetime.now()
            duration = (end_time - self.start_time).total_seconds()
            
            self.log("=" * 50)
            self.log(f"备份完成! 总耗时: {duration:.2f} 秒")
            self.log(f"成功: {success_count} 个，失败: {fail_count} 个")
            self.log(f"备份文件保存位置: {save_path}")
            
            # 显示完成消息
            messagebox.showinfo(
                "备份完成", 
                f"备份完成!\n总耗时: {duration:.2f} 秒\n"
                f"成功: {success_count} 个，失败: {fail_count} 个"
            )
            
        except Exception as e:
            self.log(f"备份过程中发生严重错误: {str(e)}")
            messagebox.showerror("错误", f"备份过程中发生严重错误:\n{str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = FileBackupTool(root)
    root.mainloop()